package pnca.analytics;

import pnca.*;
import pnca.Constants;
import MASS.*;                  // MASS Library
import java.util.*;             // Vectors, etc.
import java.util.AbstractMap.*; // for simple key-value pairs
import java.util.logging.Level;

/**
 * @name WindDivergence Wind divergence algorithm Mass implementation
 *
 * @author Niko Simonson (Primary)
 * @author Brett Yasutake (Contributing)
 */
public class WindDivergence extends Place {

    // Constants
    // MASS arguments
    private static final String NAME = Constants.ANALYTICSPACKAGE + "WindDivergence";
    private static final String[] MASSARGS = Constants.MASSARGS;   // connection arguments
    private static final int NPROCESSES = Constants.NPROCESSES;    // number of processes to employ
    private static final int NTHREADS = Constants.NTHREADS;      // number of threads to employ
    // Functions
    private static final int init_ = 0;         // initialize Places
    private static final int readWinds_ = 1;
    private static final int computeWG_ = 2;    // compute regional avg temps
    private static final int storeWGResults_ = 3;   // exchange regional temps
    // Wind gradient constants
    private static int fileRange;
    private static final int DISTANCE = Constants.DISTANCE;     // km distance between two geographic cells
    // Target variable names
    private static final String XWIND = Constants.XWIND;  // east-west windspeed at 10m above surface
    private static final String YWIND = Constants.YWIND;  // north-south windspeed at 10m above surface
    // Northwest regional climate data sample
    private static final int XRANGE = Constants.XRANGE;              // width of data set (# of grid cells)
    private static final int YRANGE = Constants.YRANGE;              // length of data set (# of grid cells)
    private static final int METADATALENGTH = Constants.METADATALENGTH;        // # of metadata elements
    private static final int MAXITERATIONS = Constants.NONTIMEBASED;
    
    // Variables
    private int myIndex;
    private double[][] xSpd;                 // E-W wind speed
    private double[][] ySpd;                 // N-S wind speed
    private double[][] xg;                          // E-W wind gradient
    private double[][] yg;                          // N-S wind gradient
    private static Places pacNorthwest = null;   // MASS parallel environment
    private ResultsAdapter ra;           // Stores results
    private static ProvenanceAdapter pa;            // Stores provenance
    
    private static SimpleEntry<String, Integer[]> entryXGradient; // x gradient result entry
    private static SimpleEntry<String, Integer[]> entryYGradient; // y gradient result entry
    private String resultsName;          // Result files' names
    private static String[] fileNames;

    /**
     * @name WindDivergence - Constructor initializes wind speed and
     *                            and gradient data structures
     * @param interval unused Empty constructor required for MASS Place
     *                 implementation
     */
    public WindDivergence(Object interval) {
        // A constructor implementation is required for MASS Place
        xSpd = new double[XRANGE][YRANGE];
        ySpd = new double[XRANGE][YRANGE];
        xg = new double[XRANGE][YRANGE];
        yg = new double[XRANGE][YRANGE];
    }

    /**
     * @name callMethod Provides enumerated list of functions to be parallelized
     * in MASS
     *
     * @param funcId - function ID (listed as Constants)
     * @param args - function parameters
     * @return - unused
     */
    public Object callMethod(int funcId, Object args) {
        switch (funcId) {
            case init_:
                return init(args);
            case readWinds_:
                return readWinds(args);
            case computeWG_:
                return computeWG(args);
            case storeWGResults_:
                return storeWGResults(args);
        }
        return null;
    }

    /**
     * @name init - Initialization information for MASS Places index[0]: days
     * index[1]: time slices in a day
     *
     * @param args - MASS initialization arguments, # of processes, # of threads
     * @return unused
     */
    public Object init(Object args) {
        // Each place keeps track of its index
        myIndex = index[0];

        return (Object) null;
    }

    /**
     * @name readWinds - Read wind magnitudes and directions from NetCDF file
     * @param args - Not used
     * @return unused
     */
    public Object readWinds(Object args) {
        try {
            // Retreive input values from input adapter
            xSpd = InputAdapter.read2DFloat(fileNames[myIndex], XWIND, XRANGE, YRANGE);
            ySpd = InputAdapter.read2DFloat(fileNames[myIndex], YWIND, XRANGE, YRANGE);
        } catch (Exception ex) {
            pa.log(Level.SEVERE, null, ex);
        }
        return (Object) null;
    }

    /**
     * @name computeMF Open netCDF file and compute wind gradient over the
     * entire region
     *
     * @param args unused
     * @return unused
     */
    public Object computeWG(Object args) {
        for (int x = 0; x < XRANGE; ++x) {
            for (int y = 0; y < YRANGE; ++y) {
                // padding boundary values equal to 0.0
                if ((x == 0) || (x == XRANGE - 1) || (y == 0) || (y == YRANGE - 1)) {
                     xg[x][y] = 0.0;
                     yg[x][y] = 0.0;
                }
                // else not at the boundary
                else {
                    // Calculate wind divergence and store
                    xg[x][y] = (xSpd[x + 1][y] - xSpd[x - 1][y]) / DISTANCE;
                    yg[x][y] = (ySpd[x][y + 1] - ySpd[x][y - 1]) / DISTANCE;
                }
            }
        }
        return (Object) null;
    }

    /**
     * @name storeWGResults Record results to the results adapter
     * @param args - Not used
     * @return unused
     */
    public Object storeWGResults(Object args) {
        try {
            // Iniitalize reference values
            Integer[] dimensions = new Integer[2];
            dimensions[0] = XRANGE;
            dimensions[1] = YRANGE;
            entryXGradient = new SimpleEntry<>("xGradient", dimensions);
            entryYGradient = new SimpleEntry<>("yGradient", dimensions);

            // Set the results output file name
            resultsName = "MassWindDivergence-" + InputAdapter.readTime(fileNames[myIndex]);
            ra = new ResultsAdapter(ResultsAdapter.DOUBLE, resultsName, fileNames[myIndex], entryXGradient, entryYGradient);

            ra.log2DDouble("xGradient", xg);
            ra.log2DDouble("yGradient", yg);


        } catch (Exception ex) {
            pa.log(Level.SEVERE, "exception caught in storeWGResults", ex);
        }
        return null;
    }
    
    public static int getMaxIterations() {
        return MAXITERATIONS;
    }
    
    /**
     * @name setFiles - Set file name array to store file names
     * @param fileArray - Array containing output result file names
     */
    public static void setFiles(String[] fileArray) {
        //find bugs - mutable object stored in static variable, make copy instead?
        fileNames = fileArray;
        fileRange = fileNames.length;
    }

    /**
     * @name initialize - Initialize MASS, Places objects, and other data
     *                    structured
     * @param status - Status adapter associated with this analytics module
     */
    public static void initialize(StatusAdapter status, int handle) {
        try {
            
            // Dummy object required by MASS
            Object[] fixer = new Object[fileRange];

            // Provenance collection
            status.reportMessage("Storing Metadata");
            SimpleEntry<String, String>[] provenanceEntries = collectProvenance();
            pa = new ProvenanceAdapter("WindGradientProvenance", fileNames[0]);
            pa.create(provenanceEntries);
            
            // Start MASS
            status.reportMessage("Starting MASS");
            MASS.init(MASSARGS, NPROCESSES, NTHREADS);
            
            // Initialize parallel computing space (Places object)
            pacNorthwest = new Places(handle, NAME, null,
                    fileRange);
            status.reportMessage("Computational Nodes created");
            
            // Initialize all computing nodes
            pacNorthwest.callAll(init_, fixer);
            status.reportMessage("Computational Nodes initialized");
        } catch (Exception ex) {
            pa.log(Level.SEVERE, null, ex);
        }
    }

    /**
     * @name runAnalysis Performs entire analysis, manages MASS
     *
     * @param status StatusModel UI view
     * @throws Exception
     */
    public static long runAnalysis(StatusAdapter status) throws Exception {
        long start, end; // Data provenance for execution time
        long totalTime = -1;

        // Dummy object to prevent array over- or underflow
        Object[] fixer = new Object[fileRange];

        try {
            // Begin time measurement
            start = new Date().getTime();

            // Read winds at each time slice
            pacNorthwest.callAll(readWinds_, fixer);
            status.reportMessage("Source values read");

            // Compute wind gradient
            pacNorthwest.callAll(computeWG_, fixer);
            status.reportMessage("Calculations completed");

            // Store results
            pacNorthwest.callAll(storeWGResults_, fixer);

            // End time measurement
            end = new Date().getTime();
            totalTime = end - start;    //milliseconds

            status.reportMessage("Results stored");

            // Report execution time
            System.err.println("Segment execution time (milliseconds): "
                    + totalTime);
            status.reportMessage("Segment execution time (milliseconds): "
                    + totalTime);
        } catch (Exception e) {
            pa.log(Level.SEVERE, null, e);
        }
        finish();
        return totalTime;
    }

    /**
     * @name collectProvenance - Returns a SimpleEntry array holding provenance
     *                           data
     * @return SimpleEntry - Holds provenance key-value pairs
     */
    private static SimpleEntry<String, String>[] collectProvenance() {
        SimpleEntry<String, String>[] provenanceEntries = new SimpleEntry[METADATALENGTH];
        provenanceEntries[0] = new SimpleEntry<>("Controlling_Node", MASSARGS[0]);
        provenanceEntries[1] = new SimpleEntry<>("Controlling_Node_Password", MASSARGS[1]);
        provenanceEntries[2] = new SimpleEntry<>("Machine_Life_File_Name", MASSARGS[2]);
        provenanceEntries[3] = new SimpleEntry<>("Port_Number", MASSARGS[3]);
        provenanceEntries[4] = new SimpleEntry<>("No_of_Processes", String.valueOf(NPROCESSES));
        provenanceEntries[5] = new SimpleEntry<>("No_of_Threads", String.valueOf(NTHREADS));
        provenanceEntries[6] = new SimpleEntry<>("No_of_Nodes", String.valueOf(fileRange));

        return provenanceEntries;
    }

    /**
     * @name finish - Terminate and clean-up MASS by calling MASS's finish
     *                function
     */
    public static void finish() {
        MASS.finish();
    }
}
